/*
  MeggyJr_GemKeeper.pde
 
 Simple match colour game.
   
 Version 1.0 - 3/1/2009
 Copyright (c) 2009 Ken Corey.  All right reserved.
 http://flippinbits.com
 http://www.evilmadscientist.com/
 
 The Splash screen, much better colour choice and true randomisation code
 courtesy of Mark White at http://codefun.com
 
 This library is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 
 This library is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this library.  If not, see <http://www.gnu.org/licenses/>.
 	  
 */

#include <MeggyJrSimple.h>    // Required code, line 1 of 2.

uint8_t barcount, barx, bary, barorient;
uint8_t bararray[3];
unsigned long gamedelay;
unsigned long TimeNow, TimeLast, TimeNext;
uint8_t gamecolours;
uint8_t screencopy[64];
unsigned long totalscore;
unsigned long totalticks;
byte TheseColors[15];

#define note 230
unsigned int music_note;
unsigned int *current_tune;

unsigned int score1[8] =
{
  ToneC3, note>>2,
  ToneE3, note>>2,
  ToneG3, note>>2,
  0xffff,0xffff
};
unsigned int score2[10] =
{
  ToneC3, note>>2,
  ToneE3, note>>2,
  ToneG3, note>>2,
  ToneC4, note>>2,
  0xffff,0xffff
};
unsigned int score3[14] =
{
  ToneC3, note>>2,
  ToneE3, note>>2,
  ToneG3, note>>2,
  ToneC4, note>>2,
  ToneE4, note>>2,
  ToneG4, note>>2,
  0xffff,0xffff
};
unsigned int score4[20] =
{
  ToneC3, note>>2,
  ToneE3, note>>2,
  ToneG3, note>>2,
  ToneC4, note>>2,
  ToneE4, note>>2,
  ToneG4, note>>2,
  ToneC4, note>>2,
  ToneE4, note>>2,
  ToneG4, note>>2,
  0xffff,0xffff
};

unsigned int charge[14] = 
{
  ToneC3, note>>1,
  ToneE3, note>>1,
  ToneG3, note>>1,
  ToneG4, note,
  ToneE3, note>>1,
  ToneG4, note,
  0xffff,0xffff
};

/* This is the tune from "The Bare Necessities" from Disney's "Jungle Book".
unsigned int music[150] = {
  ToneC3, note,
  ToneD3, note,
  ToneF3, note,
  
  0,note,
  ToneA3, 2*note,
  ToneGs3, note,
  
  ToneA3, note,
  ToneG3, note,
  ToneF3, note,
  ToneF3, note,
  
  ToneG3, note,
  ToneF3, note,
  ToneG3, note,
  ToneF3, note,
  
  ToneG3, note,
  ToneF3, note>>1,
  ToneD3, note+(note>>1),
  ToneC3, note,
  
  ToneF3, note+(note>>1),
  ToneC3, note>>1,
  ToneF3, note,
  ToneA3, note,
  
  ToneD4, note,
  ToneC4, note,
  ToneB3, note,
  ToneA3, note,
  
  ToneG3, note<<2,
  
  0, note,
  ToneC4, note,
  ToneD4, note,
  ToneC4, note,
  
  0, note,
  ToneD4, note<<1,
  ToneC4, note,
  
  ToneD4, note,
  ToneC4, note>>1,
  ToneA3, note+(note>>1),
  ToneF3, note,
  
  ToneG3, note,
  ToneF3, note,
  ToneG3, note,
  ToneF3, note,

  ToneD4, note,
  ToneF3, note,
  ToneF3, note+(note>>1),
  ToneG3, note,
  
  ToneA3, note,
  ToneC4, note,
  ToneA3, note,
  ToneC4, note,
  
  ToneA3, note,
  ToneF3, note>>1,
  ToneD3, note+(note>>1),
  ToneC3, note,
  
  ToneF3, (note<<2)+note,
  
  ToneF3, note>>1,
  ToneE3, note,
  ToneE3, note>>1,
  ToneD3, note,
  
  ToneC3, note<<1,
  ToneC3, (note<<1)+note,
  
  ToneG3, note,
  ToneG3, note>>1,
  ToneF3, note>>1,
  ToneE3, note,
  
  ToneF3, (note<<2)+note,
  ToneF3, note>>1,
  ToneE3, note,
  ToneE3, note>>1,
  ToneD3, note>>1,
  
  ToneC3, note<<1,
  ToneG3, (note<<1)+note,
  
  ToneG3, note,
  ToneF3, note,
  ToneG3, note,
  
  ToneA3, (note<<2)+note,
  
  ToneA3, note,
  ToneB3, note,
  ToneC4, note,
  
  ToneD4, note,
  ToneD4, note,
  ToneD4, note,
  ToneB3, note,
  
  ToneA3, note,
  ToneG3, note,
  ToneA3, note,
  ToneB3, note,
  
  ToneC4, note,
  ToneD4, note,
  ToneC4, note,
  ToneA3, note,
  ToneG3, note,
  ToneA3, note,
  ToneB3, note,
  ToneC4, note,
  
  ToneD4, note>>1,
  ToneD4, note>>1,
  ToneD4, note<<1,
  ToneB3, note,
  
  ToneA3, note,
  ToneG3, note,
  ToneF3, note,
  ToneE3, note,
  
  ToneF3, (note<<2)+note,
  0xffff,0xffff
};
*/  


void
Music_Update()
{
#ifndef MeggySimulator
  if (MeggyJr::ToneTimeRemaining <= 0 && current_tune) {
    // assume time for the next note.
    if (current_tune[music_note]==0xffff) {      
      current_tune=0;
      music_note=0;
    } else {
      Tone_Start(current_tune[music_note],current_tune[music_note+1]);
      music_note += 2;
    }
  }
#endif
}

void
PlayTune(int score)
{
  switch(score) {
    case 0:
      break;
    case 1:
      current_tune=score1;
      break;
    case 2:
      current_tune=score2;
      break;
    case 3:
      current_tune=score3;
      break;
    case 4:
      current_tune=score4;
      break;
    default:
      current_tune=charge;
      break;
  }
}

void
SetupGame()
{
  byte TheseButtons = 0;

#ifndef MeggySimulator
  // Inits the random number sequence...
  Serial.begin(19200);
#endif
  //Draw Intro Screen
  ClearSlate();
  DrawPx(0,7,Blue);DrawPx(0,6,Blue);DrawPx(0,5,Blue);DrawPx(0,4,Blue);DrawPx(0,3,Blue);DrawPx(1,3,Blue);DrawPx(2,3,Blue);DrawPx(3,3,Blue);DrawPx(1,7,Blue);DrawPx(2,7,Blue);DrawPx(3,7,Blue);DrawPx(3,4,Blue);DrawPx(3,5,Blue);DrawPx(2,5,Blue);
  DrawPx(1,6,Yellow);DrawPx(1,5,Yellow);DrawPx(1,4,Yellow);DrawPx(1,2,Yellow);DrawPx(2,6,Yellow);DrawPx(3,6,Yellow);DrawPx(4,6,Yellow);DrawPx(4,4,Yellow);DrawPx(2,4,Yellow);DrawPx(3,2,Yellow);DrawPx(4,2,Yellow);DrawPx(2,2,Yellow);
  DrawPx(3,1,Red);DrawPx(3,0,Red);DrawPx(4,3,Red);DrawPx(5,2,Red);DrawPx(6,3,Red);DrawPx(7,4,Red);DrawPx(7,3,Red);DrawPx(7,2,Red);DrawPx(7,1,Red);DrawPx(7,0,Red);
  DisplaySlate();
  // wait for any button push, and seed random number
#ifndef MeggySimulator
  while (!TheseButtons){TheseButtons = Meg.GetButtons();}
#endif
  ClearSlate();
  TimeNow = millis();
  TimeLast = millis();
  randomSeed(TimeNow);
  barcount=0;
  barx=0;
  bary=0;
  gamedelay=1000;
  gamecolours=3;
  TheseColors[1] = Yellow; TheseColors[2] = Blue; TheseColors[3] = Red; TheseColors[4] = Violet; TheseColors[5] = DimGreen; TheseColors[6] = DimOrange;
  TheseColors[7] = 7; TheseColors[8] = 8; TheseColors[9] = 9; TheseColors[10] = 10; TheseColors[11] = 11; TheseColors[12] = 12; TheseColors[13] = 13; TheseColors[14] = 14;
  totalscore=0;
  totalticks=0;
  SetAuxLEDs(totalscore);
  
  music_note=0;
  current_tune=charge;
//  Tone_Start(music[music_note],music[music_note+1]);
//  music_note += 2;  
}

void setup()                    // run once, when the sketch starts
{
  MeggyJrSimpleSetup();      // Required code, line 2 of 2.
  
  SetupGame();
}

void
BarDraw(uint8_t drawflag) 
{
  uint8_t colour1, colour2, colour3;
  if (drawflag) {
    colour1 = bararray[0];
    colour2 = bararray[1];
    colour3 = bararray[2];
  } else {
    colour1 = Dark;
    colour2 = Dark;
    colour3 = Dark;
  }
  
  if (barorient) {
      if (bary > 0) {
        DrawPx ( barx, bary-1, colour1);
      }
      DrawPx ( barx, bary, colour2);
      if (bary< 7) {
        DrawPx ( barx, bary+1, colour3);
      }
  } else {
      if (barx >0) {
        DrawPx ( barx-1, bary, colour1);
      }
      DrawPx ( barx, bary, colour2);
      if (barx < 7) {
        DrawPx ( barx+1, bary, colour3);
      }
  }
}

void 
DrawBar()
{
  BarDraw(1);
}

void 
EraseBar()
{
  BarDraw(0);
}

uint8_t
CollideBar(uint8_t dir)
{
  if (dir==0) {
    if (barorient) {
      // We're vertical, only check under the one pixel.    
      if (ReadPx(barx,bary-2)) {
        return 1;
      }
    } else {
      // We're horizontal, check under all 3.
      if (ReadPx(barx-1,bary-1) || ReadPx(barx,bary-1) || ReadPx(barx+1,bary-1)) {
        return 1;
      }
    }
  } else if (dir==1) { // check left collision
    if (barorient) {
      // We're vertical
      if (ReadPx(barx-1,bary+1)||ReadPx(barx-1,bary)||ReadPx(barx-1,bary-1)) {
        return 1;
      }
    } else {
      // We're horizontal, check under all 3.
      if (barx < 2 || ReadPx(barx-2,bary)) {
        return 1;
      }
    }    
  } else if (dir==2) { // check right collision
    if (barorient) {
      // We're vertical
      if (barx > 6 || ReadPx(barx+1,bary+1)||ReadPx(barx+1,bary)||ReadPx(barx+1,bary-1)) {
        return 1;
      }
    } else {
      // We're horizontal
      if (barx >6 || ReadPx(barx+2,bary)) {
        return 1;
      }
    }    
  } else if (dir==3) {
    // Check to see if we can switch from horiz to vertical or visa-versa
    if (barorient) {
      // We're vertical
      if ((barx <1) || (barx>6) || ReadPx(barx-1, bary) || ReadPx(barx+1,bary)) {
//      if ((barx <1) || ReadPx(barx-1, bary) || ReadPx(barx+1,bary)) {
        return 1;
      }
    } else {
      // We're horiz
//      if ((bary <1) || (bary>6) || ReadPx(barx, bary-1) || ReadPx(barx,bary+1)) {
      if ((bary <1) || ReadPx(barx, bary-1) || (bary<7 &&ReadPx(barx,bary+1))) {
        return 1;
      }
    }
  }
  return 0;
}

void
HandleKeys()
{
  uint8_t temp;
  
  if (barcount) {
    EraseBar();
  }
  CheckButtonsPress();
  
  if (Button_A) {
    if (!CollideBar(3)) {
      barorient = (barorient==0);
    }
  }
  if (Button_B) {
    temp = bararray[0];
    bararray[0] = bararray[1];
    bararray[1] = bararray[2];
    bararray[2] = temp;
  }
  if (Button_Left) {
    if (barx) {
      if (!CollideBar(1)) {
        barx--;
      }
    }
  }
  if (Button_Down) {
    TimeNext = TimeNow;
  }
  if (Button_Right) {
    if ((barorient &&barx < 7)||(!barorient && barx < 6)) {
      if (!CollideBar(2)) {
        barx++;
      }
    }
  }
  if (barcount) {  
    DrawBar();
  }
  DisplaySlate();
}

void
EndGame() {
  ClearSlate();
  SetupGame();
}

uint8_t
SettleCell(uint8_t col, uint8_t row) {
  if (!ReadPx(col,row) && ReadPx(col,row+1)) {
    DrawPx(col,row,ReadPx(col,row+1));
    DrawPx(col,row+1,Dark);
    return 1;
  } else {
    return 0;
  }
}

uint8_t
SettleColumn(uint8_t col) {
  uint8_t j, retcode;
  retcode=0;
  for (j=0;j<7;j++) {
    if (SettleCell(col,j)) {
      retcode=1;
    }
  }  
  return retcode;
}

void tonedelay(unsigned long deltime) {
  unsigned long iterations = deltime/50;

  do {
    Music_Update();

    delay(50);
  } while (iterations--);
}

void
SettleBar() {
  uint8_t i, retcode;

  //DisplaySlate();
  retcode=1;
  while (retcode==1) {
    retcode=0;
    for(i=0;i<8;i++) {
      if (SettleColumn(i)) {
        retcode=1;
      }
    }
    DisplaySlate();
    tonedelay(gamedelay>>3);
  }

}

void 
CopyScreen()
{
  uint8_t i,j,offset;
  
  offset=0;
  for (i=0;i<8;i++) {
    for (j=0;j<8;j++) {
      screencopy[offset++]=ReadPx(i,j);
    }
  }
}

void
WriteScreen()
{
  uint8_t i,j,offset;
  
  offset=0;
  for (i=0;i<8;i++) {
    for (j=0;j<8;j++) {
      DrawPx(i,j,screencopy[offset++]);
    }
  }
  DisplaySlate();
}

void
MyDrawPx(uint8_t x, uint8_t y, uint8_t colour)
{
  screencopy[(x<<3)+y]=colour;
}

void 
flashbar(uint8_t a,uint8_t b,uint8_t c,uint8_t m,uint8_t n,uint8_t o,uint8_t p,uint8_t q,uint8_t r, unsigned score)
{
  uint8_t temp;
  for (temp=0;temp<3;temp++) {
    DrawPx(m,n,White);
    DrawPx(o,p,White);
    DrawPx(q,r,White);
    DisplaySlate();
    tonedelay(100);
    DrawPx(m,n,a);
    DrawPx(o,p,b);
    DrawPx(q,r,c);
    DisplaySlate();
    tonedelay(100);
  }
}

uint8_t
ClearWins() {  
  uint8_t i,j, fixup,a,b,c, toggle;
  uint8_t score=0;

  CopyScreen();  
  fixup=1;
  toggle=0;
  while (fixup) {
    fixup=0;
    for(i=0;i<8;i++) {
      j=0;
      while (j<8) {
        a = ReadPx(i,j);
        b = ReadPx(i+1,j);
        c = ReadPx(i+2,j);
        if ((i<6) && a && (a == b) && (b == c)) {  // Check horiz win.
          flashbar(a,b,c,i,j,i+1,j,i+2,j,score);
          MyDrawPx(i,j,Dark);
          MyDrawPx(i+1,j,Dark);
          MyDrawPx(i+2,j,Dark);
          fixup=1;
          score++;
          PlayTune(score);
        }
        a = ReadPx(i,j);
        b = ReadPx(i,j+1);
        c = ReadPx(i,j+2);        
        if ((j<6) && a  && (a==b) && (b==c)) {  // Check vert win.
          flashbar(a,b,c,i,j,i,j+1,i,j+2,score);
          MyDrawPx(i,j,Dark);
          MyDrawPx(i,j+1,Dark);
          MyDrawPx(i,j+2,Dark);
          fixup=1;
          score++;
          PlayTune(score);
        }
        a = ReadPx(i,j);
        b = ReadPx(i+1,j+1);
        c = ReadPx(i+2,j+2);        
        if ((i<6) && (j<6) && a  && (a==b) && (b==c)) {  // Check vert win.
          flashbar(a,b,c,i,j,i+1,j+1,i+2,j+2,score);
          MyDrawPx(i,j,Dark);
          MyDrawPx(i+1,j+1,Dark);
          MyDrawPx(i+2,j+2,Dark);
          fixup=1;
          score++;
          PlayTune(score);
        }
        a = ReadPx(i,j);
        b = ReadPx(i-1,j+1);
        c = ReadPx(i-2,j+2);        
        if ((i>1) && (j<6) && a  && (a==b) && (b==c)) {  // Check vert win.
          flashbar(a,b,c,i,j,i-1,j+1,i-2,j+2,score);
          MyDrawPx(i,j,Dark);
          MyDrawPx(i-1,j+1,Dark);
          MyDrawPx(i-2,j+2,Dark);
          fixup=1;
          score++;
          PlayTune(score);
        }

        j++;
      }      
    }
    if (fixup) {
      WriteScreen();
//      DisplaySlate();
      SettleBar();
      CopyScreen();
    }
  }
  totalscore+=score;
  SetAuxLEDs(totalscore);
  return 0;
}
void 
loop()                     // run over and over again
{
  TimeNow = millis();
  
  Music_Update();
  
  HandleKeys();  

  if (TimeNow > TimeNext) { // Do a game tick
    totalticks++;
    if (totalticks & 1) {
      if (MeggyJr::ToneTimeRemaining <= 0) {
        Tone_Start(ToneC3,50);
      }
    } else {
      if (MeggyJr::ToneTimeRemaining <= 0) {
        Tone_Start(ToneG3,50);
      }
    }
    if (!(totalticks & 0x7)) { // every 8 ticks, reduce clock speed
      gamedelay-=5;
      if (gamedelay<300) {
        gamedelay=300;
      }
    }
    if (!(totalticks & 0x7f)) {  // increase game colours
        gamecolours++;
        if (gamecolours>14) {
          gamecolours=14;
        }
    }

    TimeNext=millis()+gamedelay;
    if (barcount==0) {  // create a new falling bar
        barcount++;
        barx = rand()%6+1;
        bary=7;
        barorient = rand() % 2;
        bararray[0] = TheseColors[rand() % gamecolours + 1];
        bararray[1] = TheseColors[rand() % gamecolours + 1];
        bararray[2] = TheseColors[rand() % gamecolours + 1];
        DrawBar();
        if (CollideBar(0)) {
          if (barorient) {            
            flashbar(bararray[0],bararray[1],bararray[2],
                barx,bary,barx,bary,barx,bary-1,0);
          } else {
            flashbar(bararray[0],bararray[1],bararray[2],
                barx-1,bary,barx,bary,barx+1,bary,0);
          }
          EndGame();
        }
    } else { // handle falling bar
      if (CollideBar(0)) {
        DrawBar();
        SettleBar();
        while (ClearWins());
        bary=0;
        barx=0;
        barcount=0;      
      } else {
        EraseBar();
        bary--;      
        DrawBar();
        if ((barorient && bary == 1) || (!barorient && bary==0)) {
          while (ClearWins());
          SettleBar();
          bary=0;
          barx=0;
          barcount=0;
        }
      }
    }
  }
  
  DisplaySlate();                  // Write the drawing to the screen.
}


